// HSS.Security.Cryptography.AES.cs
// ----------------------------------------------------------------------------
// Licensed under the MIT license
// http://www.opensource.org/licenses/mit-license.html
// ----------------------------------------------------------------------------
// HighSpeed-Solutions, LLC
// Copyright (c) 2001-2010
// ----------------------------------------------------------------------------
// File:       AES.cs
// Author:     HSS\gbanta
// Created:    08/12/2010
// Modified:   03/02/2011
// ----------------------------------------------------------------------------
namespace HSS.Security.Cryptography
{
	#region Using Directives
	using System;
	using System.IO;
	using System.Security.Cryptography;
	using System.Text;
	#endregion

	#region AES
	/// <summary>
	/// An Encryption helper class that performs AES Encryption/Decryption using either
	/// RSACryptoServiceProvider Public/Private Keys which are used to encrypt/decrypt
	/// a random AES Key or a user provided AES Key and AES Initialization Vector(IV).
	/// It also provides helper methods for deriving bytes using Rfc2898DeriveBytes, 
	/// generating keys using RNGCryptoServiceProvider, and generating random passwords.
	/// </summary>
	/// <remarks>
	/// <para>
	/// <b>NOTE:</b> When using the RSACryptoServiceProvider to Encrypt 
	/// the AES Key, the Key and IV are prepended to the begining of the
	/// encrypted contents. When using the RSACryptoServiceProvider to 
	/// Decrypt, it is assumed the encrypted contents has this same 
	/// information.
	/// </para>
	/// <para>
	/// Features of AES are implemented as follows...
	/// </para>
	/// <list type="table">
	/// <listheader>
	/// <term>Feature</term>
	/// <description>Description</description>
	/// </listheader>
	/// <item><term>KeySize</term><description>256 bit</description></item>
	/// <item><term>CodeBook</term><description>CBC - Cipher-Block-Chaining</description></item>
	/// <item><term>Key Management</term><description>RSA Exhange Public/Private Keys</description></item>
	/// <item><term>MachineStore/UserProfile</term><description>When accessing a KeyContainer, indicates the KeyStore (default is the UserProfile)</description></item>
	/// </list>
	/// </remarks>
	public static class AES
	{
		#region Constants

		/// <summary>
		/// The default minimum password length when generating a random password.
		/// </summary>
		public const int DEFAULT_MIN_PASSWORD_LENGTH = 8;
		/// <summary>
		/// The default maximum password length when generating a random password.
		/// </summary>
		public const int DEFAULT_MAX_PASSWORD_LENGTH = 16;

		/// <summary>
		/// A list of lower case characters used when generating a random password.
		/// </summary>
		public const string PASSWORD_CHARS_LCASE = "abcdefgijkmnopqrstwxyz";
		/// <summary>
		/// A list of upper case characters used when generating a random password.
		/// </summary>
		public const string PASSWORD_CHARS_UCASE = "ABCDEFGHJKLMNPQRSTWXYZ";
		/// <summary>
		/// /// A list of numbers used when generating a random password.		
		/// </summary>
		public const string PASSWORD_CHARS_NUMERIC = "0123456789";
		/// <summary>
		/// A list of special characters used when generating a random password.
		/// </summary>
		public const string PASSWORD_CHARS_SPECIAL = "~!@#$%^&*()_+,.<>?/";

		#endregion

		#region Fields
		private readonly static object keyLock = new object();
		private static bool isInitialized;
		private static KeyContainerService keyContainerService;
		#endregion

		#region Properties
		/// <summary>
		/// The name of the default Key Container to use when not specifying
		/// explicity the name of a key container to use; see <see cref="ConfigureAccess"/>
		/// </summary>
		public static string DefaultKeyName
		{
			get { return _keyName; }
			set { _keyName = value; }
		} static string _keyName;
		/// <summary>
		/// Gets whether or not to use the MachineStore versus the UserProfile when accessing the KeyStore (default: false) see <see cref="ConfigureAccess"/>
		/// </summary>
		public static bool UseMachineStore
		{
			get { return _useMachineStore; }
		} static bool _useMachineStore;
		/// <summary>
		/// Gets whether or not to allow prompting (if necessary) when accessing a KeyStore (default: false) see <see cref="ConfigureAccess"/>
		/// </summary>
		public static bool AllowPrompt
		{
			get { return _allowPrompt; }
		} static bool _allowPrompt;
		/// <summary>
		/// Gets the size, in bits, of the secret key used for the AES algorithm.
		/// </summary>
		public static int KeySize
		{
			get { return _keySize; }
			private set { _keySize = value; }
		} static int _keySize = 0x100;
		#endregion

		#region Methods

		/// <summary>
		/// Configure AES by indicating what KeyStore to use, and whether or not 
		/// to allow prompting (if necessary); this method can be called more than
		/// once to change the state when making subsequent calls to Encrpt/Decrypt
		/// <para>
		/// NOTE: When using MachineStore, requests to encrypt/decrypt require the calling user to have elevated privelages.
		/// </para>
		/// </summary>
		/// <param name="useMachineStore">Indicate whether to use the MachineStore or the UserProfile.</param>
		/// <param name="allowPrompt">Indicate whether or not prompting is allowed.</param>
		/// <remarks>
		/// Call this method to specify the state for subsequent calls to Encrpt/Decrypt
		/// </remarks>
		public static void ConfigureAccess(bool useMachineStore, bool allowPrompt)
		{
			if (isInitialized)
			{
				lock (keyLock)
				{
					if (isInitialized)
					{
						keyContainerService = null;
						isInitialized = false;
						AES._useMachineStore = useMachineStore;
						AES._allowPrompt = allowPrompt;
					}
				}
			}
		}

		/// <summary>
		/// Compares two Hash Values
		/// </summary>
		/// <param name="hash1">Orignial Hash</param>
		/// <param name="hash2">Compare Hash</param>
		/// <returns>true if both Hash Values are equal</returns>
		public static bool CompareHash(byte[] hash1, byte[] hash2)
		{
			bool isEqual = false;

			if (hash1.Length == hash2.Length)
			{
				int i = 0;
				while ((i < hash1.Length) && (hash1[i] == hash2[i]))
				{
					i += 1;
				}
				if (i == hash1.Length)
				{
					isEqual = true;
				}
			}

			return isEqual;
		}

		#region Encryption

		/// <summary>
		/// Encrypt a string of data.
		/// </summary>
		/// <param name="plainString">The plain string to encrypt.</param>
		/// <returns>The encrypted content as a BASE64 string</returns>
		/// <exception cref="ArgumentException">The DefaultKeyName property must be set.</exception>
		public static string Encrypt(string plainString)
		{
			Guard.ArgumentNotNullOrEmpty(AES.DefaultKeyName, "DefaultKeyName");
			AES.InitializeKeyManger();
			return Encrypt(plainString, AES.DefaultKeyName);
		}
		/// <summary>
		/// Encrypt a string of data.
		/// </summary>
		/// <param name="plainString">The plain string to encrypt.</param>
		/// <param name="keyName">The name of the RSA Key to encrypt the AES key with.</param>
		/// <returns>The encrypted content as a BASE64 string</returns>
		/// <exception cref="ArgumentException">keyName is null or empty. You must provide the name of the KeyContainer to use for Encryption</exception>
		/// <exception cref="ArgumentException">Could not find a Container with the KeyName provided</exception>
		public static string Encrypt(string plainString, string keyName)
		{
			Guard.ArgumentNotNullOrEmpty(keyName, "keyName");
			AES.InitializeKeyManger();
			Guard.ArgumentThrowIf(!keyContainerService.Exists(keyName), "keyName", "Could not find a Container with the KeyName provided.");
			using (var rsa = keyContainerService.LoadKey(keyName))
				return Encrypt(plainString, rsa);
		}
		/// <summary>
		/// Encrypt a string of data.
		/// </summary>
		/// <param name="plainString">The plain string to encrypt.</param>
		/// <param name="publicKey">The RSA Public Key information used to encrypt the AES Key. Should be the CspBlob.</param>
		/// <returns>The encrypted content as a BASE64 string</returns>
		public static string Encrypt(string plainString, byte[] publicKey)
		{
			AES.InitializeKeyManger();
			using (var rsa = new RSACryptoServiceProvider())
			{
				rsa.ImportCspBlob(publicKey);
				return Encrypt(plainString, rsa);
			}
		}
		/// <summary>
		/// Encrypt a string of data.
		/// </summary>
		/// <param name="plainString">The plain string to encrypt.</param>
		/// <param name="rsa">The RSACryptoServiceProvider used to encrypt the AES Key.</param>
		/// <returns>The encrypted content as a BASE64 string</returns>
		private static string Encrypt(string plainString, RSACryptoServiceProvider rsa)
		{
			byte[] plainBytes = Encoding.UTF8.GetBytes(plainString);
			byte[] encryptedBytes = AES.Encrypt(plainBytes, rsa);
			return Convert.ToBase64String(encryptedBytes);
		}

		/// <summary>
		/// Encrypt an array of bytes.
		/// </summary>
		/// <param name="plainBytes">The data to encrypt.</param>
		/// <returns>The encrypted byte[]</returns>
		/// <exception cref="ArgumentException">The KeyName property must be set.</exception>
		public static byte[] Encrypt(byte[] plainBytes)
		{
			Guard.ArgumentNotNullOrEmpty(AES.DefaultKeyName, "DefaultKeyName");
			AES.InitializeKeyManger();
			return Encrypt(plainBytes, AES.DefaultKeyName);
		}
		/// <summary>
		/// Encrypt an array of bytes.
		/// </summary>
		/// <param name="plainBytes">The data to encrypt.</param>
		/// <param name="keyName">The name of the RSA Key to encrypt the AES key with.</param>
		/// <returns>A byte array containing the encrypted data.</returns>
		/// <exception cref="ArgumentException">keyName is null or empty. You must provide the name of the KeyContainer to use for Encryption</exception>
		/// <exception cref="ArgumentException">Could not find a Container with the KeyName provided</exception>
		public static byte[] Encrypt(byte[] plainBytes, string keyName)
		{
			Guard.ArgumentNotNullOrEmpty(keyName, "keyName");
			AES.InitializeKeyManger();
			Guard.ArgumentThrowIf(!keyContainerService.Exists(keyName), "keyName", "Could not find a Container with the KeyName provided.");
			using (var rsa = keyContainerService.LoadKey(keyName))
				return Encrypt(plainBytes, rsa);
		}
		/// <summary>
		/// Encrypt an array of bytes.
		/// </summary>
		/// <param name="plainBytes">The data to encrypt.</param>
		/// <param name="publicKey">The RSA Public Key information used to encrypt the AES Key. Should be the CspBlob.</param>
		/// <returns>A byte array containing the encrypted data.</returns>
		public static byte[] Encrypt(byte[] plainBytes, byte[] publicKey)
		{
			AES.InitializeKeyManger();
			using (var rsa = new RSACryptoServiceProvider())
			{
				rsa.ImportCspBlob(publicKey);
				return Encrypt(plainBytes, rsa);
			}
		}
		/// <summary>
		/// Encrypt an array of bytes.
		/// </summary>
		/// <param name="plainBytes">The data to encrypt.</param>
		/// <param name="rsa">The RSACryptoServiceProvider used to encrypt the AES Key.</param>
		/// <returns>A byte array containing the encrypted data.</returns>
		private static byte[] Encrypt(byte[] plainBytes, RSACryptoServiceProvider rsa)
		{
			using (var plainStream = new MemoryStream(plainBytes))
				return Encrypt(plainStream, rsa);
		}

		/// <summary>
		/// Encrypt a stream of data.
		/// </summary>
		/// <param name="plainStream">The stream to encrypt.</param>
		/// <returns>The encrypted byte[]</returns>
		/// <exception cref="ArgumentException">The KeyName property must be set.</exception>
		public static byte[] Encrypt(Stream plainStream)
		{
			Guard.ArgumentNotNullOrEmpty(AES.DefaultKeyName, "DefaultKeyName");
			AES.InitializeKeyManger();
			return Encrypt(plainStream, AES.DefaultKeyName);
		}
		/// <summary>
		/// Encrypt a stream of data.
		/// </summary>
		/// <param name="plainStream">The stream to encrypt.</param>
		/// <param name="keyName">The name of the RSA Key to encrypt the AES key with.</param>
		/// <returns>A byte array containing the encrypted data.</returns>
		/// <exception cref="ArgumentException">keyName is null or empty. You must provide the name of the KeyContainer to use for Encryption</exception>
		/// <exception cref="ArgumentException">Could not find a Container with the KeyName provided</exception>
		public static byte[] Encrypt(Stream plainStream, string keyName)
		{
			Guard.ArgumentNotNullOrEmpty(keyName, "keyName");
			AES.InitializeKeyManger();
			Guard.ArgumentThrowIf(!keyContainerService.Exists(keyName), "keyName", "Could not find a Container with the KeyName provided.");
			using (var rsa = keyContainerService.LoadKey(keyName))
				return Encrypt(plainStream, rsa);
		}
		/// <summary>
		/// Encrypt a stream of data.
		/// </summary>
		/// <param name="plainStream">The stream to encrypt.</param>
		/// <param name="publicKey">The RSA Public Key information used to encrypt the AES Key. Should be the CspBlob.</param>
		/// <returns>A byte array containing the encrypted data.</returns>
		public static byte[] Encrypt(Stream plainStream, byte[] publicKey)
		{
			AES.InitializeKeyManger();
			using (var rsa = new RSACryptoServiceProvider())
			{
				rsa.ImportCspBlob(publicKey);
				return Encrypt(plainStream, rsa);
			}
		}
		/// <summary>
		/// Encrypt a stream of data.
		/// </summary>
		/// <param name="plainStream">The stream to encrypt.</param>
		/// <param name="rsa">The RSACryptoServiceProvider used when encrypted.</param>
		/// <returns>A byte array containing the encrypted data.</returns>
		private static byte[] Encrypt(Stream plainStream, RSACryptoServiceProvider rsa)
		{
			using (var aes = initAES())
			using (var encryptedStream = new MemoryStream())
			{
				var key = rsa.Encrypt(aes.Key, false);
				var iv = aes.IV;
				encryptedStream.Write(BitConverter.GetBytes(key.Length), 0, 4);
				encryptedStream.Write(BitConverter.GetBytes(iv.Length), 0, 4);
				encryptedStream.Write(key, 0, key.Length);
				encryptedStream.Write(iv, 0, iv.Length);
				using (var transform = aes.CreateEncryptor())
				using (var cs = new CryptoStream(encryptedStream, transform, CryptoStreamMode.Write))
				{
					Encrypt(aes.BlockSize / 8, cs, plainStream);
					return encryptedStream.ToArray();
				}
			}
		}

		/// <summary>
		/// Encrypt a plain string using the provided key and iv.
		/// </summary>
		/// <param name="plainString">The plain string to encrypt.</param>
		/// <param name="key">The secret key used when encrypted.</param>
		/// <param name="iv">The initialization vector (IV) to use for encryption.</param>
		/// <returns>A Base64 string containing the encrypted data.</returns>
		public static string Encrypt(string plainString, byte[] key, byte[] iv)
		{
			var plainBytes = UTF8Encoding.UTF8.GetBytes(plainString);
			var encryptedBytes = Encrypt(plainBytes, key, iv);
			return Convert.ToBase64String(encryptedBytes);
		}
		/// <summary>
		/// Encrypt a byte array using the provided key and iv.
		/// </summary>
		/// <param name="plainBytes">The bytes to encrypt.</param>
		/// <param name="key">The secret key used when encrypted.</param>
		/// <param name="iv">The initialization vector (IV) to use for encryption.</param>
		/// <returns>A byte array containing the encrypted data.</returns>
		public static byte[] Encrypt(byte[] plainBytes, byte[] key, byte[] iv)
		{
			using (var plainStream = new MemoryStream(plainBytes))
				return Encrypt(plainStream, key, iv);
		}
		/// <summary>
		/// Encrypt a stream using the provided key and iv.
		/// </summary>
		/// <param name="plainStream">The data stream to encrypt.</param>
		/// <param name="key">The secret key used when encrypted.</param>
		/// <param name="iv">The initialization vector (IV) to use for encryption.</param>
		/// <returns>A byte array containing the encrypted data.</returns>
		public static byte[] Encrypt(Stream plainStream, byte[] key, byte[] iv)
		{
			using (var aes = initAES())
			using (var transform = aes.CreateEncryptor(key, iv))
			using (var encryptedStream = new MemoryStream())
			using (var cs = new CryptoStream(encryptedStream, transform, CryptoStreamMode.Write))
			{
				Encrypt(aes.BlockSize / 8, cs, plainStream);
				return encryptedStream.ToArray();
			}
		}

		private static void Encrypt(int bufferSize, CryptoStream cs, Stream plainStream)
		{
			var bytesRead = 0;
			var buffer = new byte[bufferSize];
			do
			{
				bytesRead = plainStream.Read(buffer, 0, bufferSize);
				cs.Write(buffer, 0, bytesRead);
			}
			while (bytesRead > 0);
			cs.FlushFinalBlock();
		}

		#endregion

		#region Decryption

		/// <summary>
		/// Decrypt a string of encrypted text using the <see cref="DefaultKeyName"/>.
		/// </summary>
		/// <param name="base64String">The BASE64 String containing the encrypted data.</param>
		/// <returns>The decrypted data</returns>
		/// <exception cref="ArgumentException">The DefaultKeyName property must be set prior to calling this method.</exception>
		public static string Decrypt(string base64String)
		{
			Guard.ArgumentNotNullOrEmpty(AES.DefaultKeyName, "DefaultKeyName");
			AES.InitializeKeyManger();
			return Decrypt(base64String, AES.DefaultKeyName);
		}
		/// <summary>
		/// Decrypt a BASE64 string of encrypted text.
		/// </summary>
		/// <param name="base64String">The BASE64 String containing the encrypted data.</param>
		/// <param name="keyName">The name of the RSA Private Key used to decrypt the AES key.</param>
		/// <returns>The decrypted data.</returns>
		/// <exception cref="ArgumentException">keyName is null or empty. You must provide the name of the KeyContainer to use for Decryption</exception>
		/// <exception cref="ArgumentException">Could not find a Container with the KeyName provided</exception>
		public static string Decrypt(string base64String, string keyName)
		{
			Guard.ArgumentNotNullOrEmpty(keyName, "keyName");
			AES.InitializeKeyManger();
			Guard.ArgumentThrowIf(!keyContainerService.Exists(keyName), "keyName", "Could not find a Container with the KeyName provided.");
			using (var rsa = keyContainerService.LoadKey(keyName))
				return Decrypt(base64String, rsa);
		}
		/// <summary>
		/// Decrypt a BASE64 string of encrypted text.
		/// </summary>
		/// <param name="base64String">The BASE64 String containing the encrypted data.</param>
		/// <param name="privateKey">The RSA Private Key information used to decrypt the AES key. Should be the CspBlob.</param>
		/// <returns>The decrypted data</returns>
		public static string Decrypt(string base64String, byte[] privateKey)
		{
			AES.InitializeKeyManger();
			using (var rsa = new RSACryptoServiceProvider())
			{
				rsa.ImportCspBlob(privateKey);
				return Decrypt(base64String, rsa);
			}
		}
		/// <summary>
		/// Decrypt a string of encrypted text
		/// </summary>
		/// <param name="base64String">The BASE64 String containing the encrypted data.</param>
		/// <param name="rsa">The RSACryptoServiceProvider used to decrypt the AES Key.</param>
		/// <returns>The decrypted data</returns>
		private static string Decrypt(string base64String, RSACryptoServiceProvider rsa)
		{
			byte[] encryptedBytes = Convert.FromBase64String(base64String);
			byte[] decryptedBytes = Decrypt(encryptedBytes, rsa);
			return Encoding.UTF8.GetString(decryptedBytes);
		}

		/// <summary>
		/// Decrypt a byte array of encrypted text using the <see cref="DefaultKeyName"/>
		/// </summary>
		/// <param name="encryptedBytes">The byte array containing the encrypted data.</param>
		/// <returns>A byte array containing the decrypted data.</returns>
		/// <exception cref="ArgumentException">The DefaultKeyName property must be set prior to calling this method.</exception>
		public static byte[] Decrypt(byte[] encryptedBytes)
		{
			Guard.ArgumentNotNullOrEmpty(AES.DefaultKeyName, "DefaultKeyName");
			AES.InitializeKeyManger();
			return Decrypt(encryptedBytes, AES.DefaultKeyName);
		}
		/// <summary>
		/// Decrypt a byte array of encrypted text
		/// </summary>
		/// <param name="encryptedBytes">The byte array containing the encrypted data.</param>
		/// <param name="keyName">The name of the RSA Private Key used to decrypt the AES key.</param>
		/// <returns>A byte array containing the decrypted data.</returns>
		/// <exception cref="ArgumentException">keyName is null or empty. You must provide the name of the KeyContainer to use for Encryption</exception>
		/// <exception cref="ArgumentException">Could not find a Container with the KeyName provided</exception>
		public static byte[] Decrypt(byte[] encryptedBytes, string keyName)
		{
			Guard.ArgumentNotNullOrEmpty(keyName, "keyName");
			AES.InitializeKeyManger();
			Guard.ArgumentThrowIf(!keyContainerService.Exists(keyName), "keyName", "Could not find a Container with the KeyName provided.");
			using (var rsa = keyContainerService.LoadKey(keyName))
				return Decrypt(encryptedBytes, rsa);
		}
		/// <summary>
		/// Decrypt a byte array of encrypted text
		/// </summary>
		/// <param name="encryptedBytes">The byte array containing the encrypted data.</param>
		/// <param name="privateKey">The RSA Private Key information used to decrypt the AES key. Should be the CspBlob.</param>
		/// <returns>A byte array containing the decrypted data.</returns>
		public static byte[] Decrypt(byte[] encryptedBytes, byte[] privateKey)
		{
			AES.InitializeKeyManger();
			using (var rsa = new RSACryptoServiceProvider())
			{
				rsa.ImportCspBlob(privateKey);
				return Decrypt(encryptedBytes, rsa);
			}
		}
		/// <summary>
		/// Decrypt a byte array of encrypted text
		/// </summary>
		/// <param name="encryptedBytes">The byte array containing the encrypted data.</param>
		/// <param name="rsa">The RSACryptoServiceProvider used to decrypt the AES Key.</param>
		/// <returns>A byte array containing the decrypted data.</returns>
		private static byte[] Decrypt(byte[] encryptedBytes, RSACryptoServiceProvider rsa)
		{
			using (var encryptedStream = new MemoryStream(encryptedBytes))
				return Decrypt(encryptedStream, rsa);
		}

		/// <summary>
		/// Decrypt a stream of encrypted data.
		/// </summary>
		/// <param name="encryptedStream">The stream to unencrypt.</param>
		/// <returns>A byte array containing the decrypted data.</returns>
		/// <exception cref="ArgumentException">The DefaultKeyName property must be set prior to calling this method.</exception>
		public static byte[] Decrypt(Stream encryptedStream)
		{
			Guard.ArgumentNotNullOrEmpty(AES.DefaultKeyName, "DefaultKeyName");
			AES.InitializeKeyManger();
			return Decrypt(encryptedStream, AES.DefaultKeyName);
		}
		/// <summary>
		/// Decrypt a stream of encrypted data.
		/// </summary>
		/// <param name="encryptedStream">The stream to unencrypt.</param>
		/// <param name="keyName">The name of the RSA Private Key used to decrypt the AES key.</param>
		/// <returns>A byte array containing the decrypted data.</returns>
		/// <exception cref="ArgumentException">keyName is null or empty. You must provide the name of the KeyContainer to use for Encryption</exception>
		/// <exception cref="ArgumentException">Could not find a Container with the KeyName provided</exception>
		public static byte[] Decrypt(Stream encryptedStream, string keyName)
		{
			Guard.ArgumentNotNullOrEmpty(keyName, "keyName");
			AES.InitializeKeyManger();
			Guard.ArgumentThrowIf(!keyContainerService.Exists(keyName), "keyName", "Could not find a Container with the KeyName provided.");
			using (var rsa = keyContainerService.LoadKey(keyName))
				return Decrypt(encryptedStream, rsa);
		}
		/// <summary>
		/// Decrypt a stream of encrypted data.
		/// </summary>
		/// <param name="encryptedStream">The stream to unencrypt.</param>
		/// <param name="privateKey">The RSA Private Key information used to decrypt the AES key. Should be the CspBlob.</param>
		/// <returns>A byte array containing the decrypted data.</returns>
		public static byte[] Decrypt(Stream encryptedStream, byte[] privateKey)
		{
			AES.InitializeKeyManger();
			using (var rsa = new RSACryptoServiceProvider())
			{
				rsa.ImportCspBlob(privateKey);
				return Decrypt(encryptedStream, rsa);
			}
		}
		/// <summary>
		/// Decrypt a stream of encrypted data.
		/// </summary>
		/// <param name="encryptedStream">The stream to unencrypt.</param>
		/// <param name="rsa">The RSACryptoServiceProvider used to decrypt the AES Key.</param>
		/// <returns>A byte array containing the decrypted data.</returns>
		private static byte[] Decrypt(Stream encryptedStream, RSACryptoServiceProvider rsa)
		{
			var LenK = new byte[4];
			var LenIV = new byte[4];

			encryptedStream.Seek(0, SeekOrigin.Begin);

			encryptedStream.Read(LenK, 0, LenK.Length);
			encryptedStream.Read(LenIV, 0, LenIV.Length);

			var key = new byte[BitConverter.ToInt32(LenK, 0)];
			var iv = new byte[BitConverter.ToInt32(LenIV, 0)];

			encryptedStream.Read(key, 0, key.Length);
			encryptedStream.Read(iv, 0, iv.Length);

			key = rsa.Decrypt(key, false);

			return Decrypt(encryptedStream, key, iv);
		}

		/// <summary>
		/// Decrypt a BASE64 string using the provided key and iv.
		/// </summary>
		/// <remarks>This method is typical for applications where an RSA public/private key cannot be used.</remarks>
		/// <param name="base64String">The BASE64 String containing the encrypted data.</param>
		/// <param name="key">The secret key used when encrypted.</param>
		/// <param name="iv">The initialization vector (IV) used when encrypted.</param>
		/// <returns>A string containing the decrypted data.</returns>
		public static string Decrypt(string base64String, byte[] key, byte[] iv)
		{
			var encryptedBytes = Convert.FromBase64String(base64String);
			var decryptedBytes = Decrypt(encryptedBytes, key, iv);
			return UTF8Encoding.UTF8.GetString(decryptedBytes);
		}
		/// <summary>
		/// Decrypt a byte[] using the provided key and iv.
		/// </summary>
		/// <remarks>This method is typical for applications where an RSA public/private key cannot be used.</remarks>
		/// <param name="encryptedBytes">The bytes to decrypt.</param>
		/// <param name="key">The secret key used when encrypted.</param>
		/// <param name="iv">The initialization vector (IV) used when encrypted.</param>
		/// <returns>A byte array containing the decrypted data.</returns>
		public static byte[] Decrypt(byte[] encryptedBytes, byte[] key, byte[] iv)
		{
			using (var encryptedStream = new MemoryStream(encryptedBytes))
				return Decrypt(encryptedStream, key, iv);
		}
		/// <summary>
		/// Decrypt a Stream using the provided key and iv.
		/// </summary>
		/// <remarks>This method is typical for applications where an RSA public/private key cannot be used.</remarks>
		/// <param name="encryptedStream">The stream to decrypt.</param>
		/// <param name="key">The secret key used when encrypted.</param>
		/// <param name="iv">The initialization vector (IV) used when encrypted.</param>
		/// <returns>A byte array containing the decrypted data.</returns>
		public static byte[] Decrypt(Stream encryptedStream, byte[] key, byte[] iv)
		{
			using (var aes = initAES())
			using (var transform = aes.CreateDecryptor(key, iv))
			using (var plainStream = new MemoryStream())
			using (var cs = new CryptoStream(plainStream, transform, CryptoStreamMode.Write))
			{
				Decrypt(aes.BlockSize / 8, cs, encryptedStream);
				return plainStream.ToArray();
			}
		}

		private static void Decrypt(int bufferSize, CryptoStream cs, Stream encryptedStream)
		{
			var bytesRead = 0;
			var buffer = new byte[bufferSize];
			do
			{
				bytesRead = encryptedStream.Read(buffer, 0, bufferSize);
				cs.Write(buffer, 0, bytesRead);
			}
			while (bytesRead > 0);
			cs.FlushFinalBlock();
		}

		#endregion

		#region Generators

		/// <summary>
		/// Generates a cryptographically strong sequence of random bytes using <see cref="RNGCryptoServiceProvider"/> and the provided <paramref name="keySize"/>.
		/// </summary>
		/// <param name="keySize">The size of the key.</param>
		/// <returns>A cryptographically strong sequence of random bytes.</returns>
		public static byte[] GenerateRNGKey(int keySize)
		{
			var data = new byte[keySize];
			if (rng == null)
				rng = new RNGCryptoServiceProvider();
			rng.GetBytes(data);
			return data;
		} private static RNGCryptoServiceProvider rng;

		/// <summary>
		/// Derive a random key from the provided Password, Salt, Iterations and KeySize using <see cref="Rfc2898DeriveBytes"/>.
		/// </summary>
		/// <remarks>
		/// The DeriveBytes method takes a password, a salt, an iteration count and key size, and 
		/// generates a random key through a call to the <see cref="Rfc2898DeriveBytes"/>.<see cref="Rfc2898DeriveBytes.GetBytes"/> method.
		/// </remarks>
		/// <param name="password">The Password used to generate the Key</param>
		/// <param name="salt">The Value to prepend the Password with for uniqueness</param>
		/// <param name="iterations">The number of Iterations in generating the key</param>
		/// <param name="keySize">The number of pseudo-random key bytes to generate.</param>
		/// <returns>Returns a pseudo-random key from a password, salt and iteration count.</returns>
		public static byte[] DeriveBytes(byte[] password, byte[] salt, int iterations, int keySize)
		{
			// Generate password, which will be used to derive the key.
			Rfc2898DeriveBytes passwordBytes = new Rfc2898DeriveBytes(
													   password,
													   salt,
													   iterations);

			// Convert key to a byte array using the desired number of bytes.
			return passwordBytes.GetBytes(keySize);
		}
		/// <summary>
		/// Derive a random key from the provided Password, Salt, Iterations and KeySize using <see cref="Rfc2898DeriveBytes"/>.
		/// </summary>
		/// <remarks>
		/// The DeriveBytes method takes a password, a salt, an iteration count and key size, and 
		/// generates a random key through a call to the <see cref="Rfc2898DeriveBytes"/>.<see cref="Rfc2898DeriveBytes.GetBytes"/> method.
		/// </remarks>
		/// <param name="password">The Password used to generate the Key</param>
		/// <param name="salt">The Value to prepend the Password with for uniqueness</param>
		/// <param name="iterations">The number of Iterations in generating the key</param>
		/// <param name="keySize">The number of pseudo-random key bytes to generate.</param>
		/// <returns>Returns a pseudo-random key from a password, salt and iteration count.</returns>
		public static byte[] DeriveBytes(string password, byte[] salt, int iterations, int keySize)
		{
			// Generate password, which will be used to derive the key.
			Rfc2898DeriveBytes passwordBytes = new Rfc2898DeriveBytes(
													   password,
													   salt,
													   iterations);

			// Convert key to a byte array using the desired number of bytes.
			return passwordBytes.GetBytes(keySize);
		}
		/// <summary>
		/// Derive a random key from the provided Password, Salt, Iterations and KeySize using <see cref="Rfc2898DeriveBytes"/>.
		/// </summary>
		/// <remarks>
		/// The DeriveBytes method takes a password, a salt, an iteration count and key size, and 
		/// generates a random key through a call to the <see cref="Rfc2898DeriveBytes"/>.<see cref="Rfc2898DeriveBytes.GetBytes"/> method.
		/// </remarks>
		/// <param name="password">The Password used to generate the Key</param>
		/// <param name="salt">The Value to prepend the Password with for uniqueness</param>
		/// <param name="iterations">The number of Iterations in generating the key</param>
		/// <param name="keySize">The number of pseudo-random key bytes to generate.</param>
		/// <returns>Returns a pseudo-random key from a password, salt and iteration count.</returns>
		public static byte[] DeriveBytes(string password, string salt, int iterations, int keySize)
		{
			// Generate password, which will be used to derive the key.
			byte[] salted = UTF8Encoding.UTF8.GetBytes(salt);
			Rfc2898DeriveBytes passwordBytes = new Rfc2898DeriveBytes(
													   password,
													   salted,
													   iterations);

			// Convert key to a byte array using the desired number of bytes.
			return passwordBytes.GetBytes(keySize);
		}

		/// <summary>
		/// Generates a random password.
		/// </summary>
		/// <returns>
		/// Randomly generated password.
		/// </returns>
		/// <remarks>
		/// The length of the generated password will be determined at
		/// random. It will be no shorter than the minimum default and
		/// no longer than maximum default.
		/// </remarks>
		public static string GenerateRandom()
		{
			return GenerateRandom(DEFAULT_MIN_PASSWORD_LENGTH,
							DEFAULT_MAX_PASSWORD_LENGTH);
		}
		/// <summary>
		/// Generates a random password of the exact length.
		/// </summary>
		/// <param name="length">
		/// Exact password length.
		/// </param>
		/// <returns>
		/// Randomly generated password.
		/// </returns>
		public static string GenerateRandom(int length)
		{
			return GenerateRandom(length, length);
		}
		/// <summary>
		/// Generates a random password.
		/// </summary>
		/// <param name="minLength">
		/// Minimum password length.
		/// </param>
		/// <param name="maxLength">
		/// Maximum password length.
		/// </param>
		/// <returns>
		/// Randomly generated password.
		/// </returns>
		/// <remarks>
		/// The length of the generated password will be determined at
		/// random and it will fall with the range determined by the
		/// function parameters.
		/// </remarks>
		public static string GenerateRandom(int minLength, int maxLength)
		{
			// Make sure that input parameters are valid.
			if (minLength <= 0 || maxLength <= 0 || minLength > maxLength)
				return null;

			// Create a local array containing supported password characters
			// grouped by types. You can remove character groups from this
			// array, but doing so will weaken the password strength.
			char[][] charGroups = new char[][] 
											{
												PASSWORD_CHARS_LCASE.ToCharArray(),
												PASSWORD_CHARS_UCASE.ToCharArray(),
												PASSWORD_CHARS_NUMERIC.ToCharArray(),
												PASSWORD_CHARS_SPECIAL.ToCharArray()
											};

			// Use this array to track the number of unused characters in each
			// character group.
			int[] charsLeftInGroup = new int[charGroups.Length];

			// Initially, all characters in each group are not used.
			for (int i = 0; i < charsLeftInGroup.Length; i++)
				charsLeftInGroup[i] = charGroups[i].Length;

			// Use this array to track (iterate through) unused character groups.
			int[] leftGroupsOrder = new int[charGroups.Length];

			// Initially, all character groups are not used.
			for (int i = 0; i < leftGroupsOrder.Length; i++)
				leftGroupsOrder[i] = i;

			// Because we cannot use the default randomizer, which is based on the
			// current time (it will produce the same "random" number within a
			// second), we will use a random number generator to seed the
			// randomizer.

			// Use a 4-byte array to fill it with random bytes and convert it then
			// to an integer value.
			byte[] randomBytes = new byte[4];

			// Generate 4 random bytes.
			RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
			rng.GetBytes(randomBytes);

			// Convert 4 bytes into a 32-bit integer value.
			int seed = (randomBytes[0] & 0x7f) << 24 |
						randomBytes[1] << 16 |
						randomBytes[2] << 8 |
						randomBytes[3];

			// Now, this is real randomization.
			Random random = new Random(seed);

			// This array will hold password characters.
			char[] password = null;

			// Allocate appropriate memory for the password.
			if (minLength < maxLength)
				password = new char[random.Next(minLength, maxLength + 1)];
			else
				password = new char[minLength];

			// Index of the next character to be added to password.
			int nextCharIdx;

			// Index of the next character group to be processed.
			int nextGroupIdx;

			// Index which will be used to track not processed character groups.
			int nextLeftGroupsOrderIdx;

			// Index of the last non-processed character in a group.
			int lastCharIdx;

			// Index of the last non-processed group.
			int lastLeftGroupsOrderIdx = leftGroupsOrder.Length - 1;

			// Generate password characters one at a time.
			for (int i = 0; i < password.Length; i++)
			{
				// If only one character group remained unprocessed, process it;
				// otherwise, pick a random character group from the unprocessed
				// group list. To allow a special character to appear in the
				// first position, increment the second parameter of the Next
				// function call by one, i.e. lastLeftGroupsOrderIdx + 1.
				if (lastLeftGroupsOrderIdx == 0)
					nextLeftGroupsOrderIdx = 0;
				else
					nextLeftGroupsOrderIdx = random.Next(0,
														 lastLeftGroupsOrderIdx);

				// Get the actual index of the character group, from which we will
				// pick the next character.
				nextGroupIdx = leftGroupsOrder[nextLeftGroupsOrderIdx];

				// Get the index of the last unprocessed characters in this group.
				lastCharIdx = charsLeftInGroup[nextGroupIdx] - 1;

				// If only one unprocessed character is left, pick it; otherwise,
				// get a random character from the unused character list.
				if (lastCharIdx == 0)
					nextCharIdx = 0;
				else
					nextCharIdx = random.Next(0, lastCharIdx + 1);

				// Add this character to the password.
				password[i] = charGroups[nextGroupIdx][nextCharIdx];

				// If we processed the last character in this group, start over.
				if (lastCharIdx == 0)
					charsLeftInGroup[nextGroupIdx] =
											  charGroups[nextGroupIdx].Length;
				// There are more unprocessed characters left.
				else
				{
					// Swap processed character with the last unprocessed character
					// so that we don't pick it until we process all characters in
					// this group.
					if (lastCharIdx != nextCharIdx)
					{
						char temp = charGroups[nextGroupIdx][lastCharIdx];
						charGroups[nextGroupIdx][lastCharIdx] =
									charGroups[nextGroupIdx][nextCharIdx];
						charGroups[nextGroupIdx][nextCharIdx] = temp;
					}
					// Decrement the number of unprocessed characters in
					// this group.
					charsLeftInGroup[nextGroupIdx]--;
				}

				// If we processed the last group, start all over.
				if (lastLeftGroupsOrderIdx == 0)
					lastLeftGroupsOrderIdx = leftGroupsOrder.Length - 1;
				// There are more unprocessed groups left.
				else
				{
					// Swap processed group with the last unprocessed group
					// so that we don't pick it until we process all groups.
					if (lastLeftGroupsOrderIdx != nextLeftGroupsOrderIdx)
					{
						int temp = leftGroupsOrder[lastLeftGroupsOrderIdx];
						leftGroupsOrder[lastLeftGroupsOrderIdx] =
									leftGroupsOrder[nextLeftGroupsOrderIdx];
						leftGroupsOrder[nextLeftGroupsOrderIdx] = temp;
					}
					// Decrement the number of unprocessed groups.
					lastLeftGroupsOrderIdx--;
				}
			}

			// Convert password characters into a string and return the result.
			return new string(password);
		}

		#endregion

		/// <summary>
		/// initalizes the AES Class.
		/// </summary>
		/// <returns>An instance of AesManaged</returns>
		private static AesManaged initAES()
		{
			return new AesManaged
			{
				KeySize = KeySize,
				Mode = CipherMode.CBC
			};
		}

		/// <summary>
		/// Initialize the KeyManager
		/// </summary>
		private static void InitializeKeyManger()
		{
			if (!isInitialized)
			{
				lock (keyLock)
				{
					if (!isInitialized)
					{
						keyContainerService = new KeyContainerService(UseMachineStore, AllowPrompt);
						isInitialized = true;
					}
				}
			}
		}

		#endregion
	}
	#endregion
}